<?php

namespace App\Common\ScarecrowProvider;

/**
 * 行为日志操作表
 * Class AllHandleLog
 * @package app\common\api
 */
class ScarecrowDoLogProvider
{
    //日志写入表
    private $logTableName = "sc_do_handle_logs";
    //默认的日志文件目录
    private $logFileDirName = "";
    //所有需要写入日志的列表
    private $allLog = [];
    //所有的日志目录
    private $logFilePathList = [];
    //是否写入日志文件
    private $isWriteFile = false;
    //默认的日志文件名称
    private $defaultFileName = "";
    //日志文件是否区分天
    private $logFileIsDivideDay = true;

    //操作日志类
    private static $obj = '';

    protected function __construct()
    {
        //禁止new此函数
        $this->setDerive(env('ACTION_LOG_WRITE_TYPE', 'mysql'));
        $this->setLogFileIsDivideDay(env('ACTION_LOG_FILE_IS_DIVIDE_DAY', true));
    }

    /**
     * 处理文件夹地址
     * @param $path
     * @return string
     */
    protected function handleDirPath($path) {
        return rtrim(preg_replace('/((\\\)+|(\/)+)/i', DIRECTORY_SEPARATOR, trim($path)), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
    }

    /**
     * 设置文件日志路劲
     * @param $path
     * @return $this
     */
    public function setLogFilePath($path = "")
    {
        $path = (string)$path;
        $path = $path ?: storage_path('logs/corelog/' . $this->getMethodPath());
        $this->logFileDirName = $this->handleDirPath($path);
        $this->logFilePathList[] = $this->logFileDirName;
        return $this;
    }

    /**
     * 设置文件名称
     * @param string $logName
     * @return $this
     */
    public function setLogFileName($logName="") {
        $logName = (string)$logName;
        $tempFileName = $logName ? : "lumen_do.log";
        $tempFileNameInfo = pathinfo($tempFileName);
        $ext = $tempFileNameInfo['extension'] ?? 'log';
        $fileName = $this->logFileIsDivideDay ? $tempFileNameInfo['filename'] . "_" . date("Y-m-d") : $tempFileNameInfo['filename'];
        $this->defaultFileName = $fileName . '.' . $ext;
        return $this;
    }

    /**
     * 设置驱动
     * @param string $drive
     * @return $this
     * @throws \Exception
     */
    public function setDerive($drive = "mysql")
    {
        $this->isWriteFile = $drive == "mysql" ? false : true;
        return $this;
    }

    /**
     * 获取日志存储文件地址
     * @return string
     * @throws \Exception
     */
    private function getLogFilePath() {
        $this->defaultFileName || $this->setLogFileName();
        $this->logFileDirName || $this->setLogFilePath();
        $filePath = $this->logFileDirName . $this->defaultFileName;
        $this->logFileDirName = "";
        return $filePath;
    }

    /**
     * 创建文件夹
     * @param $path
     * @return bool
     */
    protected function createFileDir($path)
    {
        if (is_dir($path)) {
            return true;
        }
        @mkdir($path, 0777, true);
        @chmod($path, 0777);
        return is_dir($path);
    }

    /**
     * 获取操作日志实例类
     * @return ScarecrowDoLogProvider|string
     * @throws \Exception
     */
    public static function getExample()
    {
        if (self::$obj) {
            return self::$obj;
        } else {
            self::$obj = new self();
            return self::$obj;
        }
    }

    /**
     * 添加一条APP日志记录
     * @param $type 操作类型 u/d/i
     * @param $data 操作数据
     * @param string $remark 操作备注
     * @param string $method 操作的方法
     */
    public function addLog($type, $data, $remark = '', $token = '', $method = '')
    {
        $remark = addslashes($remark);
        $method = empty($method) ? $this->getMethod() : $method;
        $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $type = !empty($type) ? $type : 'n';
        $token = $token ?: (defined('APP_USER_TOKEN') ? APP_USER_TOKEN : '');
        $src = defined('APP_USER_TOKEN_SRC') ? APP_USER_TOKEN_SRC : 0;
        if ($token) {
            switch ($src) {
                case 1:
                    $userInfoObj = \App\Models\BackHome\AuthManage\LoginModel::getUserInfo($token);
                    break;
                case 2:
                    $userInfoObj = \App\Models\AppHome\AuthManage\LoginModel::getUserInfo($token);
                    break;
                case 3:
                    $userInfoObj = \App\Models\MiniHome\AuthManage\LoginModel::getUserInfo($token);
                    break;
                case 4:
                    $userInfoObj = \App\Models\WeChatOauthHome\AuthManage\LoginPcModel::getUserInfo($token);
                    break;
                default:
                    $userInfoObj = \App\Models\BackHome\AuthManage\LoginModel::getUserInfo($token);
            }

            if ($userInfoObj['code'] == RETURN_SUCCESS) {
                $userInfo = $userInfoObj['data'];
                $userId = $userInfo['id'] ?? 0;
                $userName = $userInfo['nike_name'] ?? "";
            } else {
                $userId = 0;
                $userName = "";
            }
        } else {
            $userId = 0;
            $userName = "";
        }

        $baseIp = $this->getClientIp();
        $ip = $this->isWriteFile ? $baseIp : ip2long($baseIp);
        $route = request()->url();
        $times = time();
        $remark = "用户 " . $userName . " [{$baseIp} " . date("Y-m-d H:i:s", $times) . "]:" . $remark;
        $logFilePath = $this->getLogFilePath();
        $this->allLog[$logFilePath][] = [
            'user_id' => $userId,
            'type' => $type,
            'method' => $this->isWriteFile ? $method : addslashes($method),
            'data' => $data,
            'remark' => $remark,
            'ip' => $ip ?: 0,
            'route' => $route,
            'created_at' => $this->isWriteFile ? date("Y-m-d H:i:s", $times) : $times,
            'src' => $src,
        ];
    }

    /**
     * 获取上一个方法名称
     * @@param $extType string 类与函数的连接字符
     * @return string
     */
    private function getMethod()
    {
        $backList = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        $backObj = isset($backList[2]) ? $backList[2] : false;
        if ($backObj) {
            $method = (isset($backObj['class']) ? $backObj['class'] : $backObj['file']) . (isset($backObj['type']) ? $backObj['type'] : "::") . (isset($backObj['function']) ? $backObj['function'] : '');
        } else {
            $method = '';
        }
        return $method;
    }

    /**
     * 获取方法路径写法(用于日志自动生成对应方法文件夹)
     * @return string
     */
    public function getMethodPath() {
        $backList = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
        $backObj = isset($backList[4]) ? $backList[4] : false;
        if ($backObj) {
            $method = (isset($backObj['class']) ? $backObj['class'] : $backObj['file']) . DIRECTORY_SEPARATOR . (isset($backObj['function']) ? $backObj['function'] : '');
        } else {
            $method = '';
        }
        return $method;
    }

    /**
     * 获取客户端IP
     * @return bool
     */
    private function getClientIp()
    {
        $ip = FALSE;
        //客户端IP 或 NONE
        if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
            $ip = $_SERVER["HTTP_CLIENT_IP"];
        }

        //多重代理服务器下的客户端真实IP地址（可能伪造）,如果没有使用代理，此字段为空
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
            if ($ip) {
                array_unshift($ips, $ip);
                $ip = FALSE;
            }
            for ($i = 0; $i < count($ips); $i++) {
                if (!preg_match("/^(10|172.16|192.168)./", $ips[$i])) {
                    $ip = $ips[$i];
                    break;
                }
            }
        }

        //客户端IP 或 (最后一个)代理服务器 IP
        return ($ip ? $ip : $_SERVER['REMOTE_ADDR'] ?? "");
    }

    /**
     * 将数据日志写入数据库
     * @return bool
     */
    private function writeLog()
    {
        if (empty($this->allLog)) {
            return true;
        }

        $sql = "INSERT INTO {$this->logTableName}(`user_id`,`type`,`method`,`data`,`remark`,`ip`,`route`,`created_at`,`src`) VALUES ";

        foreach ($this->allLog as $tempAllLog) {
            foreach ($tempAllLog as $item) {
                $sql .= ArrToSql($item) . ",";
            }
        }
        $sql = rtrim($sql, ',');
        \Illuminate\Support\Facades\DB::insert($sql);
    }

    /**
     * 写入日志到文件
     * @return bool
     */
    private function writeLogToFile()
    {
        if (empty($this->allLog)) {
            return true;
        }

        foreach ($this->logFilePathList as $logFilePath) {
            if (!$this->createFileDir($logFilePath)) {
                throw new \Exception("创建日志目录失败");
            }
        }

        foreach ($this->allLog as $logFilePath => $tempAllLog) {
            $fileObj = fopen($logFilePath, 'a+');
            foreach ($tempAllLog as $item) {
                $str = '[' . $item['created_at'] . " " . $item['ip'] . ']: USER_ID=>' . $item['user_id'] . '; | TYPE=>' . $item['type'] . '; | METHOD=>' . $item['method'] . '; | DATA=>' . $item['data'] . '; | REMARK=>' . $item['remark'] . ";" . PHP_EOL;
                fwrite($fileObj, $str);
            }
            fclose($fileObj);
        }

        return true;
    }

    /**
     * 设置是否按天分割文件
     * @param bool $isDivideDay
     */
    public function setLogFileIsDivideDay($isDivideDay = true) {
        $this->logFileIsDivideDay = $isDivideDay === false ? false : true;
    }

    /**
     * 日志持久化
     */
    public function __destruct()
    {
        if (env('APP_ENV') === 'testing') {
            return false;
        }

        try {
            if ($this->isWriteFile) {
                $this->writeLogToFile();
            } else {
                $this->writeLog();
            }
        } catch (\Throwable $exception) {
            //TODO:: 如果写入日志失败为了不影响接口状态，则忽略此次日志写入
        }
    }
}
